package rank;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Tuple;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class RedisLeaderboardService implements LeaderboardService {

    private static final String LEADERBOARD_KEY = "game_leaderboard";
    private final JedisPool jedisPool;


    public RedisLeaderboardService(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }


    @Override
    public void updateScore(String playerId, int score, long timestamp) {
        try (Jedis jedis = jedisPool.getResource()) {
            double encodedScore = score * 1e13 + (1e13 - timestamp);
            jedis.zadd(LEADERBOARD_KEY, encodedScore, playerId);
        }

    }


    @Override
    public RankInfo getPlayerRank(String playerId) {
        try (Jedis jedis = jedisPool.getResource()) {
            Long rank = jedis.zrevrank(LEADERBOARD_KEY, playerId);
            Double score = jedis.zscore(LEADERBOARD_KEY, playerId);
            return rank != null ?
                    new RankInfo(playerId, decodeScore(score), rank + 1) :
                    new RankInfo(playerId, 0, -1);
        }
    }

    @Override
    public List<RankInfo> getTopN(int n) {
        try (Jedis jedis = jedisPool.getResource()) {
            Set<Tuple> tuples = jedis.zrevrangeWithScores(LEADERBOARD_KEY, 0, n-1);
            return tuples.stream().map(t -> new RankInfo(t.getElement(), decodeScore(t.getScore()), 0)).collect(Collectors.toList());
        }
    }

    private int decodeScore(double encodedScore) {
        return (int) (encodedScore / 1e13);
    }


    @Override
    public List<RankInfo> getPlayerRankRange(String playerId, int range) {
        try (Jedis jedis = jedisPool.getResource()) {
            Long rank = jedis.zrevrank(LEADERBOARD_KEY, playerId);
            if (rank == null) {
                return List.of();
            }
            long start = Math.max(0, rank - range);
            long end = Math.min(jedis.zcard(LEADERBOARD_KEY) - 1, rank + range);
            return jedis.zrevrangeWithScores(LEADERBOARD_KEY, start, end)
                    .stream()
                    .map(entry -> getPlayerRank(entry.getElement()))
                    .collect(Collectors.toList());
        }
    }
}
